/**
 * \file client.c
 * \brief Codice sorgente del client
 * \author Marco Luisi
 */
#include "cars.h"
#include "cars_messaging.h"
#include "client.h"
#include "comsock.h"

#include <ctype.h>
#include <errno.h>
#include <fnmatch.h>

#include <limits.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>

#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>



char user_input[MAX_CANON +1];
char *msg_buffer;
char *sock_name;

int buf_lenght = 0;
int serverSk_fd = -1;
int clientSk_fd = -1;
int server;
int wr;

message_t outgoing;
message_t incoming;

pid_t pid;

pthread_t uTid;
pthread_t sTid;


volatile sig_atomic_t pending_signal = 0;
volatile sig_atomic_t go = 1;

/**
 * Main del client, si limita a chiamare la funzione di inizializzazione e avviare i thread, di cui poi attende la terminazione
 * @param argc
 * @param argv
 * @return
 */
int main(int argc, char **argv){
	if(argc != 2){
		printf(CLIENT_COMMAND);
		exit(EXIT_FAILURE);
	}
	if(client_init(argv[1]) == -1){
		exit(EXIT_FAILURE);
	}
	/*Avvio dei threads */
	pthread_create(&uTid, NULL, userThread, (void*) NULL);
	pthread_create(&sTid, NULL, serverThread, (void*) NULL);
	/*Attesa terminazione dei threads */
	pthread_join(sTid,NULL);
	pthread_join(uTid,NULL);
	closeSocket(serverSk_fd);
	if(unlink(sock_name) != 0){
		perror("Errore nella chiusura del socket");
	}
	exit(EXIT_SUCCESS);
}

/**
 * @brief Controlla la correttezza del nome utente e imposta la connessione al server.
 *
 * @param username nome utente passato da riga di comando
 * @return esito della funzione di inizializzazione
 */
int client_init(char *username){
	char *login;
	char *pwd;
	char *skt;
	int login_len = 0;
	int name_len = 0;
	int pwd_len = 0;
	int skt_len = 0;

	EC_M1(signal_handling());

	printf("Collegamento dell'utente %s al sistema.\n",username);
	name_len = strlen(username);
	if( name_len > LUSERNAME){
		printf("Errore: il nome utente deve contenere al massimo %d caratteri!\n", LUSERNAME);
		return(-1);
	}
	/*Creazione socket lato client*/
	pid = getpid();
	sock_name = malloc(SOCKPATH_LEN + name_len + MAX_PID_DIGITS + 1 );// +1 +6
	snprintf(sock_name,MAX_PID_DIGITS + name_len +7,"%s%s%d",CLIENT_SOCKPATH,username,pid);
	skt_len = strlen(sock_name);
	(void)unlink(SOCK_PATH);
	clientSk_fd = createServerChannel(sock_name);

	/*Connessione al socket del server*/
	serverSk_fd = openConnection(SOCK_PATH);
	if (serverSk_fd == -1) {
		switch(errno) {
			case E2BIG: {
				fprintf(stderr, "Errore nell'apertura del socket, path oltre il limite.\n");
				break;
			}

			default: {
				fprintf(stderr, "Errore nell'apertura del socket:\n");
				perror("Socket:");
				break;
			}
		}
		return (-1);
	}
	/*Acquisizione ed invio password*/
	while (pwd_len <= 1) {
		fprintf(stdout, "Inserire la password.\n");
		while (fgets(user_input, MAX_CANON, stdin) == NULL) {
		}
		pwd_len = strlen(user_input);
	}
	login_len = name_len + pwd_len + skt_len + 3; /* 3 separatori /0 */
	login = malloc(login_len);
	strncat(login, username, name_len);
	pwd = &(login[name_len + 1]);
	strncat(pwd, user_input, pwd_len - 1);
	skt = &(pwd[pwd_len + 1]);
	strncat(skt, sock_name, skt_len);
	/*Invio della richistra di login*/
	newMessage(MSG_CONNECT, login, &outgoing, login_len + 2); //1 è considerato in pwd_len dallo \n
	wr = sendMessage(serverSk_fd, &outgoing);
	printf("inviato buffer dim %d\n", wr);
	free(login);
	/*Inviata la richiesta di login si attende la risposta sulla propria socket*/
	server = acceptConnection(clientSk_fd);

	if (server == -1) {
		printf("Errore nella ricezione dell'autenticazione\n");
		if (errno == EINTR) {
			//continue;
		}
		/*Gestione errore*/
		return(-1);
	}

	if(receiveMessage(server, &incoming) == -1){
		printf("Errore nella ricezione dell'autenticazione\n");
		return(-1);
	}

	switch (incoming.type) {

	case (MSG_NO): {
		printf("%s\n", incoming.buffer);
		return(-1);
		break;
	}

	case (MSG_OK): {
		printf("Utente collegato al server!\n");
		return(0);
		break;
	}

	}

	return (0);
}

/**
 * @brief Thread dedicato alla ricezione dell'input dell'utente
 * @brief Controlla la correttezza dell'input e invia i comandi al server.
 * @param c
 */
void* userThread(void *c){
	int snd;
	sigset_t set;

	struct sigaction *sa = calloc(sizeof(struct sigaction),1);
	/*Si mascherano tutti i segnali per il thread worker*/
	sigfillset(&set);
	pthread_sigmask(SIG_SETMASK,&set,NULL);
	memset(sa,sizeof(sa),0);
	sa->sa_handler = SIG_IGN;
	sigaction(SIGQUIT, sa, NULL);
	sa->sa_handler = sigpipe_handler;
	sigaction(SIGPIPE, sa, NULL);

	sa->sa_handler = sigint_handler;
	sigaction(SIGINT, sa, NULL);

	free(sa);

	while(go){
		//occhio a EOF che chiude l'input
		printf("ciclo userthread\n");
		printf("prima di switch usertrehad\n");

		switch(parse_input(&outgoing)){

		case(ERR):{
			printf("Errore: formato del comando non valido\n");
			break;
		}

		case(HELP):{
			printf("Help!\n");
			break;
		}

		case(EXIT):{
			go = 0;
		}
		/* no break */

		case(OFFER):{

		}
		/* no break */

		case(REQ):{
		}
		/* no break */
		/*Il messaggio in uscita è già stato impostato da parse_input*/
		snd = sendMessage(server,&outgoing);
		if (snd == -1 && errno == EINTR){
			printf("send message interrotta da interruzione,client in uscita\n");
			pthread_cancel(sTid);
			pthread_exit(NULL);
		}
		printf("messaggio inviato, esito %d\n",snd);
		/* no break */
		}
		//fare freeee ogni volta del buffer
		//!!!!!!!!!!!!!!!!!!!!!!
		//MAH
		if(pending_signal == 1){
			printf("signal userthread\n");
			pthread_cancel(sTid);
			pthread_exit(NULL);
		}
		if(go == 0){
			printf("go signal userthread\n");
			pthread_exit(NULL);
		}


	}



pthread_exit(NULL);

}






/**
 *
 * @param c
 */
void* serverThread(void* c){

	int rcv;
	sigset_t set;

	struct sigaction *sa = calloc(sizeof(struct sigaction),1);
	/*Si mascherano tutti i segnali per il thread worker*/
	sigfillset(&set);
	pthread_sigmask(SIG_SETMASK,&set,NULL);
	memset(sa,sizeof(sa),0);
	sa->sa_handler = SIG_IGN;
	sigaction(SIGQUIT, sa, NULL);
	sa->sa_handler = sigpipe_handler;
	sigaction(SIGPIPE, sa, NULL);

	sa->sa_handler = sigint_handler;
	sigaction(SIGINT, sa, NULL);

	free(sa);

	//GEST SEGNALI
//	sigset_t set;
//	struct sigaction *sa;
//	sa = calloc(sizeof(struct sigaction), 1);
//	/*Si mascherano tutti i segnali per il thread worker*/
//	if (sigfillset(&set) == -1){
//		return (void*)(NULL);//mah
//	}
//	if (pthread_sigmask(SIG_SETMASK,&set,NULL) == -1){
//			return (void*)NULL;//mah
//	}
//	memset(sa,sizeof(sa),0);
//	sa->sa_handler = SIG_IGN;
//	sigaction(SIGQUIT, sa, NULL);
////EC ANCHE SU SIGACTION
//	/*Handlers del thread*/
//	sa->sa_handler = sigpipe_handler;
//	sigaction(SIGPIPE, sa, NULL);
//	sa->sa_handler = sigterm_handler;
//	EC_M1(sigaction(SIGTERM, sa, NULL));
//	sa->sa_handler = sigint_handler;
//	EC_M1(sigaction(SIGINT, sa, NULL));


	//free(sa);


	//FINE GEST SEGNALI

	//signal_handling();


	printf("server thread\n");




	while(go){
		printf("ciclo serverthread\n");
		rcv = receiveMessage(server,&incoming);
		if (rcv == -1 && errno == EINTR){
			printf("userthread dopo sendmsg eintr \n");
			pthread_cancel(uTid);
			pthread_exit(NULL);
		}
		//che fare per errno != EINTR!?!?!
		if (rcv == -1 && errno == ENOTCONN){
			printf("userthread dopo sendmsg ENOTCONN \n");
			pthread_cancel(uTid);
			pthread_exit(NULL);
		}

		printf("SERVTHREAD DOPO RECEIVE tipo %c len %d !%s!\n",incoming.type, incoming.length,incoming.buffer);
//		if(receiveMessage(server,&incoming)){//EC
//		printf("errore server chiuso\n");
//		pthread_exit(NULL);
//		}
		//MAH




		if(pending_signal == 1){
			printf("signal serverthread\n");
			pthread_cancel(uTid);
			pthread_exit(NULL);
		}
		if(go == 0){
			printf("go signal server hread\n");
			pthread_exit(NULL);
		}


	}
	printf("fine server thread\n");
	pthread_exit(NULL);

}




int parse_input(message_t *msg){
	//si riutilizza buffer input
	unsigned int buf_len;
	//pruima usavo un buffer apposta per sta funz... mah
	/*Lettura e setup del buffer*/
	//resettare prima il buffer?

	if(fgets(user_input,MAX_CANON,stdin) == NULL){ //ok?!
		if (errno == EINTR){
			printf("userthread parse input lettura stdin eintr \n");
			pthread_cancel(sTid);
			pthread_exit(NULL);
		}


		pthread_exit(NULL);
		//CAPIRE COME GESTRIRE EOF!=!=!=!=!!!

		//controllo EOF?!?!
		go = 0;
		return(ERR);
	}
	if(user_input[0] == EOF){
		printf("EOF\n");
		pthread_exit(NULL);
	}


	buf_len = strlen(user_input);
	if(buf_len == 1){ //rivedere la dim giusta
		//OCIO A EOF!!!!!
		return (-1);
	}
	buf_len = strlen(user_input);
	user_input[buf_len-1] = '\0';
	printf("input buf trimmato !%s!\n",user_input);

	if(buf_len == -1){
		return (-1);
	}

	/*Si verifica la validità dell'input da utente*/

	if(strcmp(user_input,EXIT_STRING) == 0){
		msg->type = MSG_EXIT;
		msg->buffer = NULL;
		msg->length = 0;
		printf("exit\n");
		return(EXIT);
	}
	if(strcmp(user_input,HELP_STRING) == 0){
		printf("help\n");
		return(HELP);
	}


	if(fnmatch(OFFER_PATTERN,user_input,0) == 0){
		msg->type = MSG_OFFER;
		msg->buffer = user_input;
		msg->length = strlen(user_input)+1;
		printf("OFFER %s\n", msg->buffer);
		return(OFFER);
	}

	if(fnmatch(REQ_PATTERN,user_input,0) == 0){
		msg->type = MSG_REQUEST;
		msg->buffer = &user_input[3];
		msg->length = strlen(user_input)-2;
		printf("REQ !%s!\n",msg->buffer);
		return(REQ);
	}

	printf("NO MATCH\n");
	return(ERR);

}

int signal_handling(){
	sigset_t set;
	struct sigaction *sa;
	//perchè non dichiararla invece di allocarla?

	/* Si mascherano tutti i segnali prima di installare gli opportuni gestori */
	EC_NULL_M1(sa = calloc(1,sizeof(struct sigaction))); //perchè calloc?
	EC_M1(sigfillset(&set))
	EC_M1(pthread_sigmask(SIG_SETMASK,&set,NULL))
	memset(&sa, sizeof(sa), 0);
	sa->sa_handler = SIG_IGN;
	//EC_M1(sigaction(SIGPIPE, sa, NULL));
	EC_M1(sigaction(SIGQUIT, sa, NULL));

	/* Gestione dei segnali */
	sa->sa_handler = sigterm_handler;
	EC_M1(sigaction(SIGTERM, sa, NULL));
	sa->sa_handler = sigint_handler_main;
	EC_M1(sigaction(SIGINT, sa, NULL));
	//nuovo testare bene
	sa->sa_handler = sigpipe_handler;
	EC_M1(sigaction(SIGPIPE, sa, NULL));


	/* Rimozione della maschera */
	EC_M1(sigemptyset(&set));
	EC_M1(pthread_sigmask(SIG_SETMASK, &set, NULL));
	free(sa);
	return(0);
}

static void sigint_handler(int signum){
	printf("SIGINT\n");
	go = 0;
	pending_signal = 1;
	pthread_cancel(uTid);
	pthread_cancel(sTid);

}

static void sigterm_handler(int signum){
	printf("SIGTERM\n");
	pending_signal = 1;
	go = 0;
	pthread_cancel(uTid);
	pthread_cancel(sTid);
}
static void sigpipe_handler(int signum){//nuovo
	printf("SIGPIPE\n");
	pthread_cancel(uTid);
	pthread_cancel(sTid);
	pending_signal = 1;
	go = 0;
}

static void sigint_handler_main(int signum){
	printf("SIGINT MAIN\n");
	pthread_cancel(uTid);
	pthread_cancel(sTid);
}
